package gameengine;
import java.util.ArrayList;

import gameobjects.Ammo;
import gameobjects.Boss;
import gameobjects.Bullet;
import gameobjects.Crate;
import gameobjects.EnemyEntity;
import gameobjects.Entity;
import gameobjects.Actor;
import gameobjects.PlayerEntity;
import gameobjects.Soldier;
import gameobjects.Swordsman;
import gameobjects.Weapon;
import gameobjects.Zombie;
import gui.Screen;
import inputmanager.InputMgr;


public class GameEngine {
	
	private final int 		FRAMES_PER_SECOND = 60;
	private final int 		SKIP_TICKS = 1000 / FRAMES_PER_SECOND;
	private long 			lastTick;
	private boolean 		isGameRunning;
	private Screen			mainScreen;
	private GameMgr			gameMgr;
	private EntityMgr		entityMgr;
	private PlatformMgr		platformMgr;
	private Entity			camera;
	private InputMgr		inputMgr;
	private HiScoreMgr		hiScoreMgr;

	public GameEngine(){
		this.initGame();
	}
	
	public void initGame(){		
		gameMgr = new GameMgr();
		entityMgr = new EntityMgr();
		inputMgr = new InputMgr();
		platformMgr = new PlatformMgr();
		hiScoreMgr = new HiScoreMgr();
		camera = entityMgr.createEntity("camera", null, -720, 0, 0, 0);
		mainScreen = new Screen(gameMgr,entityMgr,camera,inputMgr,hiScoreMgr);
	}
	
	public void updateLastTick(){
		lastTick = System.currentTimeMillis();
	}
		
	public void gameLoop(){
		this.updateLastTick();
		isGameRunning = true;
		while(isGameRunning){
			long currentTick = System.currentTimeMillis();
			long remaining = lastTick + SKIP_TICKS - currentTick;
			
			this.gameTick(currentTick);
			mainScreen.renderTick(currentTick);
			if (remaining > 0){
				try {
					Thread.sleep(remaining);
				} catch (InterruptedException e) {
				}
			}
			this.updateLastTick();
		}
		mainScreen.dispose();
	}
	
	public void camFollowEntity( Entity ent ){			// camera focuses the given entity and move with it
		if (ent.getX()-200 < 0)
			camera.setX(0);
		else if(ent.getX()-200 > gameMgr.getStageLen() - Screen.WIDTH)
			camera.setX(gameMgr.getStageLen() - Screen.WIDTH);
		else
			camera.setX(ent.getX()-200);
	}
	
	public void controlEntity( Actor ent ){			// input keys move the given entity
		if (inputMgr.getKeyState('D')) {
    		if (ent.getX() + ent.getMoveSpeed() >=  + gameMgr.getStageLen() - ent.getW())
    			ent.setX(gameMgr.getStageLen() - ent.getW());
    		else
    			ent.move(ent.getMoveSpeed(), 0);
    	}
    	else if (inputMgr.getKeyState('A')) {
    		if (ent.getX() >= ent.getMoveSpeed())
    			ent.move(-1*ent.getMoveSpeed(), 0);
    		else
    			ent.setX(0);
   		}
		if (inputMgr.getKeyState('W')){
			ArrayList<Integer> platforms = platformMgr.getPlatformsAt(ent.getX());
			if (platforms.contains(ent.getY())){
				//gameMgr.setTime("jump" + ent.getHandle(), System.currentTimeMillis());
				gameMgr.setTime("jumpStartHeight" + ent.getHandle(), ent.getY()); // using the hashMap for storing time to store the start height of the jump
				gameMgr.setTime("jumpInProgress" + ent.getHandle(), 1);
			}
		}
		if(inputMgr.getKeyState('O')){
			if(ent instanceof PlayerEntity)
				((PlayerEntity)ent).fire(entityMgr, System.currentTimeMillis());
		}
	}
	
	public void gameTick(long tick){
	    int gameState = gameMgr.getGameState();
	    Long nextTime = gameMgr.getTime("startTime");
	    if (gameState == GameMgr.STATE_PREMENU){
	    	if (camera.getX() < 0 && !inputMgr.getKeyState('O'))
	    		camera.move(6, 0);
	    	else
	    		gameMgr.setGameState(GameMgr.STATE_MENU);
	    }
	    else if (gameState == GameMgr.STATE_MENU){
	    	gameMgr.resetScore();
	    	gameMgr.resetLives();
	    	if(nextTime == null){
		    	if (inputMgr.getKeyState('s') && !inputMgr.getKeyState('w'))
		    		gameMgr.moveMenuSelect(+1,GameMgr.MENU_COUNT);
		    	else if (inputMgr.getKeyState('w') && !inputMgr.getKeyState('s'))
		    		gameMgr.moveMenuSelect(-1,GameMgr.MENU_COUNT);
		    	
		    	if (!(inputMgr.getKeyState('w') || inputMgr.getKeyState('s')))
		    		if (inputMgr.getKeyState('o')){
		    			int menuSel = gameMgr.getMenuSelect();
		    			if (menuSel == 0){		// PLAY
				    		gameMgr.setTime("startTime",2000 + tick);
				    		gameMgr.setStage(1);
				    		gameMgr.setLives(3);
		    			}
		    			else if (menuSel == 1){	// LOAD
		    				gameMgr.setMenuSelect(0);
		    	    		gameMgr.setGameState(GameMgr.STATE_LOAD);
		    			}
		    			else if (menuSel == 2)	// DIFFICULTY
		    				gameMgr.toggleDiff();
		    			else if (menuSel == 3)	// HI-SCORES
		    	    		gameMgr.setGameState(GameMgr.STATE_HISCORE);
		    			else if (menuSel == 4)	// HELP
		    	    		gameMgr.setGameState(GameMgr.STATE_HELP);
		    			else if (menuSel == 5)	// CREDITS
		    	    		gameMgr.setGameState(GameMgr.STATE_CREDITS);
		    			else if (menuSel == 6)	// EXIT
		    				isGameRunning = false;
		    		}
	    	}
	    	else if (tick > nextTime){
	    		gameMgr.setGameState(GameMgr.STATE_LIVES);
	    		gameMgr.setTime("startTime",2500 + tick);
	    	}
	    }
	    else if (gameState == GameMgr.STATE_LIVES){
	    	if (tick > nextTime || inputMgr.getKeyState('o')){
	    		gameMgr.setGameState(GameMgr.STATE_GAME);
	    		onLoadStage(gameMgr.getStage());
	    		gameMgr.setTime("stageEndTime",300000 + tick);
	    	}
		}
	    else if (gameState == GameMgr.STATE_GAME){
		    gameMgr.removeTime("startTime");
	    	Long dTime = gameMgr.getTime("death");
	    	if (dTime == null){
		    	if (inputMgr.getKeyState('e')){
		    		gameMgr.setGameState(GameMgr.STATE_PAUSE);
		    		gameMgr.setMenuSelect(0);
		    	}	    	
		    	else{ 
		    		controlEntity( entityMgr.getPlayerEntity() );
		    	}
		    	
		    	camFollowEntity(entityMgr.getPlayerEntity());
		    	processCollisions();
		    	processAI();
		    	//GRAVITY, JUMP and BULLET TRAVEL
		    	for (int i = 0; i < entityMgr.getEntityList().size(); i++) {
		    		Entity ent = entityMgr.getEntityList().get(i);
		    		Long jStartHeight = gameMgr.getTime("jumpStartHeight" + ent.getHandle());
		    		Long jumpInProgress = gameMgr.getTime("jumpInProgress" + ent.getHandle());
		    		if (ent.getHandle() == entityMgr.getPlayerEntity().getHandle() && jStartHeight != null && jumpInProgress == 1 ){
	    				entityMgr.getEntityList().get(i).move(0, (int)( ((Actor)ent).getJumpHeight() - ent.getY() + jStartHeight)/10 + 15 );
	    				if ( ent.getY() >= jStartHeight + ((Actor)ent).getJumpHeight() ) {
	    					gameMgr.setTime("jumpInProgress" + ent.getHandle(), 0 );
	    				}
		    			continue;
		    		}
		    		if (ent instanceof Actor){
		    			ArrayList<Integer> platforms = platformMgr.getPlatformsAt(ent.getX());
		    			boolean found = false;
		    			if (ent.getHandle() != entityMgr.getPlayerEntity().getHandle() || !inputMgr.getKeyState('S')){
			    			int s = ent.getY();
			    			int e = ent.getY() - ((Actor)ent).getGravity();
			    			for (int j = s; j >= e ; j--) {
			    				if (platforms.contains(new Integer(j))){
			    					ent.setY(j);
			    					((Actor)ent).setGravity(0);
			    					found = true;
			    					break;		    					
			    				}
							}
		    			}
		    			if(!found){
		    				((Actor)ent).fall();
		    				if (ent.getY() != 0)
		    					((Actor)ent).incrementGravity(2);
		    			}
		    		}
		    		else if (ent instanceof Bullet){
		    			Bullet b = (Bullet)ent;
		    			if (tick - b.getLaunchTime() > b.getLifeTime())
		    				entityMgr.destroyEntity(b.getHandle());
		    			else{
		    				int homType = b.getHomingType();
		    				if (homType == 0)
			    				b.move(b.getSpeed(),0);
		    				else{
		    					Actor target = entityMgr.getPlayerEntity();
		    					if (b.getTeam() == 0){
		    						ArrayList<EnemyEntity> enList = entityMgr.getEnemyList();
		    						target = enList.get(b.getHandle()%enList.size());
		    					}
		    					if (homType == 1){
				    				b.move(b.getSpeed(),0);
				    				if (target.getY() + target.getH() / 2 > b.getY() + b.getH() / 2)
				    					b.move(0, 1);
				    				else if (target.getY() + target.getH() / 2 < b.getY() + b.getH() / 2)
				    					b.move(0, -1);
		    					}
		    					else{
		    						int deltaX = target.getX() + target.getW() / 2 - (b.getX() + b.getW() / 2);
		    						int deltaY = target.getY() + target.getH() / 2 - (b.getY() + b.getH() / 2);
		    						int dist = (int)Math.sqrt(Math.pow(deltaX,2) + Math.pow(deltaY,2));
		    						int speed = Math.abs(b.getSpeed());
		    						int moveX = deltaX * speed / dist;
		    						int moveY = deltaY * speed / dist;
		    						b.move(moveX, moveY);
		    					}
		    				}		    					
		    			}
		    		}
				}
		    }
	    	else if (tick > dTime){
	    		gameMgr.removeTime("death");
	    		int newLives = gameMgr.getLives() - 1;
	    		gameMgr.setLives(newLives);
	    		onStageReset();
	    		if (newLives > 0){
		    		gameMgr.setGameState(GameMgr.STATE_LIVES);
		    		gameMgr.setTime("startTime",2500 + tick);
	    		}
		    		else{
		    		gameMgr.setGameState(GameMgr.STATE_GAME_LOSE);
		    		gameMgr.setTime("startTime",3000 + tick);
	    		}
	    	}
			
		}
	    else if (gameState == GameMgr.STATE_PAUSE){
	    	if (inputMgr.getKeyState('e')){
	    		gameMgr.setGameState(GameMgr.STATE_GAME);	    		
	    	}
	    	
	    	if (inputMgr.getKeyState('s') && !inputMgr.getKeyState('w'))
	    		gameMgr.moveMenuSelect(+1,2);
	    	else if (inputMgr.getKeyState('w') && !inputMgr.getKeyState('s'))
	    		gameMgr.moveMenuSelect(-1,2);
	    	
	    	if (inputMgr.getKeyState('o')){  
	    		int menuSel = gameMgr.getMenuSelect();
    			if (menuSel == 0){
    	    		gameMgr.setGameState(GameMgr.STATE_GAME);
    			}
    			else{
    	    		gameMgr.setGameState(GameMgr.STATE_MENU);
    	    		gameMgr.setMenuSelect(0);    				
    			}
    		}
		}
	    else if (gameState == GameMgr.STATE_CREDITS){
	    	if (inputMgr.getKeyState('o')){
	    		gameMgr.setGameState(GameMgr.STATE_MENU);	    		
	    	}
	    }
	    else if (gameState == GameMgr.STATE_HELP){
	    	if (inputMgr.getKeyState('o')){
	    		gameMgr.setGameState(GameMgr.STATE_MENU);	    		
	    	}	    	
	    }
	    else if (gameState == GameMgr.STATE_HISCORE){
	    	if (inputMgr.getKeyState('o')){
	    		gameMgr.setMenuSelect(3);
	    		gameMgr.setGameState(GameMgr.STATE_MENU);	    		
	    	}	    	
	    }
	    else if (gameState == GameMgr.STATE_LOAD){
	    	if (inputMgr.getKeyState('s') && !inputMgr.getKeyState('w'))
	    		gameMgr.moveMenuSelect(+1,4);
	    	else if (inputMgr.getKeyState('w') && !inputMgr.getKeyState('s'))
	    		gameMgr.moveMenuSelect(-1,4);
	    	
	    	if (inputMgr.getKeyState('o')){  
	    		int menuSel = gameMgr.getMenuSelect();
    			if (menuSel == 3){
    	    		gameMgr.setGameState(GameMgr.STATE_MENU);
    	    		gameMgr.setMenuSelect(1);
    			}
    			else{
    	    	gameMgr.setGameState(GameMgr.STATE_LIVES);
    	    	gameMgr.setTime("startTime",2500 + tick);
	    		gameMgr.setStage(2 + menuSel);
	    		gameMgr.setLives(3);
			}
	    	}	    	
	    }
	    else if (gameState == GameMgr.STATE_GAME_LOSE || gameState == GameMgr.STATE_GAME_WIN){
	    	if (tick > nextTime){
	    		gameMgr.removeTime("startTime");
	    		if (hiScoreMgr.isScoreHighEnough(gameMgr.getScore())){
	    			gameMgr.resetName();
		    		gameMgr.setGameState(GameMgr.STATE_SCORE_ENTER);
		    		gameMgr.setMenuSelect(0);
	    		}
	    		else{
		    		gameMgr.setGameState(GameMgr.STATE_MENU);
		    		gameMgr.setMenuSelect(0);
	    		}
	    	}
	    }
	    else if (gameState == GameMgr.STATE_SCORE_ENTER){
	    	if (inputMgr.getKeyState('o')){
	    		hiScoreMgr.addScore(gameMgr.getScore(), gameMgr.getName());    
	    		gameMgr.setGameState(GameMgr.STATE_HISCORE);		
	    	}
	    	else{
		    	if (inputMgr.getKeyState('d') && !inputMgr.getKeyState('a'))
		    		gameMgr.moveMenuSelect(+1,GameMgr.NAME_SIZE);
		    	else if (inputMgr.getKeyState('a') && !inputMgr.getKeyState('d'))
		    		gameMgr.moveMenuSelect(-1,GameMgr.NAME_SIZE);
		    	else if (inputMgr.getKeyState('s') && !inputMgr.getKeyState('w'))
			    	gameMgr.moveName(gameMgr.getMenuSelect(), -1);
			    else if (inputMgr.getKeyState('w') && !inputMgr.getKeyState('s'))
			    	gameMgr.moveName(gameMgr.getMenuSelect(), +1);
	    	}
	    }
	    inputMgr.resetPress();
	}
	
	public void onLoadStage(int stage){
		entityMgr.getPlayerEntity();
		if (stage == 1) {
			platformMgr.addPlatform(0, 140, 210);
			platformMgr.addPlatform(0, 275, 335);
			platformMgr.addPlatform(340, 210, 375);
			platformMgr.addPlatform(405, 80, 125);
			platformMgr.addPlatform(590, 80, 130);
			platformMgr.addPlatform(785, 115, 60);
			platformMgr.addPlatform(655, 275, 315);
			platformMgr.addPlatform(910, 145, 190);
			platformMgr.addPlatform(1035, 210, 125);
			entityMgr.createZombie(200, 50);
			entityMgr.createZombie(800, 400);
			entityMgr.createZombie(650, 450);
			entityMgr.createBoss(1000, 200);
			entityMgr.createCrate(700, 0);
			if (gameMgr.getDifficulty()){
				entityMgr.createZombie(1200, 300);
				entityMgr.createZombie(800, 300);
			}
			else
				entityMgr.createCrate(150, 0);
			gameMgr.setStageLen(1320);
		}
		else if (stage == 2){
			platformMgr.addPlatform(53*5,36*5,(88-53)*5);
			platformMgr.addPlatform(97*5,50*5,(132-97)*5);
			platformMgr.addPlatform(140*5,22*5,(195-140)*5);
			platformMgr.addPlatform(197*5,36*5,(264-197)*5);
			entityMgr.createZombie(200, 50);
			entityMgr.createSwordsman(800, 400);
			entityMgr.createSwordsman(1050, 450);
			entityMgr.createBoss(1000, 200);
			entityMgr.createCrate(700, 0);
			if (gameMgr.getDifficulty()){
				entityMgr.createZombie(1200, 300);
				entityMgr.createSwordsman(800, 300);
			}
			else
				entityMgr.createCrate(150, 0);
			gameMgr.setStageLen(1320);
		}
		else if (stage == 3){
			platformMgr.addPlatform(0*405/220,83*405/220,(43-0)*405/220);
			platformMgr.addPlatform(125*405/220,83*405/220,(267-125)*405/220);
			platformMgr.addPlatform(205*405/220,132*405/220,(395-205)*405/220);
			platformMgr.addPlatform(396*405/220,67*405/220,(490-396)*405/220);
			platformMgr.addPlatform(491*405/220,132*405/220,(650-491)*405/220);
			entityMgr.createSwordsman(600, 50);
			entityMgr.createSoldier(800, 400);
			entityMgr.createSoldier(650, 450);
			entityMgr.createBoss(1000, 200);
			entityMgr.createCrate(700, 0);
			if (gameMgr.getDifficulty()){
				entityMgr.createSwordsman(1200, 300);
				entityMgr.createZombie(800, 300);
			}
			else
				entityMgr.createCrate(150, 0);
			gameMgr.setStageLen(1320);
		}
		else if (stage == 4){
			platformMgr.addPlatform(0*405/220,94*405/220,(86-0)*405/220);
			platformMgr.addPlatform(98*405/220,114*405/220,(206-98)*405/220);
			platformMgr.addPlatform(242*405/220,74*405/220,(395-242)*405/220);
			platformMgr.addPlatform(451*405/220,82*405/220,(717-451)*405/220);
			platformMgr.addPlatform(484*405/220,131*405/220,(605-484)*405/220);
			entityMgr.createSoldier(200, 50);
			entityMgr.createSoldier(800, 400);
			entityMgr.createSoldier(650, 450);
			entityMgr.createBoss(1000, 200);
			entityMgr.createCrate(700, 0);
			if (gameMgr.getDifficulty()){
				entityMgr.createSoldier(1200, 300);
				entityMgr.createZombie(800, 300);
			}
			else
				entityMgr.createCrate(150, 0);
			gameMgr.setStageLen(1320);
		}
	}
	
	public void onEntityDeath(Entity e){
		if (e instanceof PlayerEntity)
    		gameMgr.setTime("death", System.currentTimeMillis() + 1000);
		else if (e instanceof Boss){
			gameMgr.addScore(1000);
			gameMgr.addScore(((int)(gameMgr.getTime("stageEndTime") - System.currentTimeMillis()) / 1000)*100);
    		if (gameMgr.getStage() == 4)
    			gameMgr.setGameState(GameMgr.STATE_GAME_WIN);
    		else{
        		gameMgr.setGameState(GameMgr.STATE_LIVES);
    			gameMgr.incStage();
    		}
    		gameMgr.setTime("startTime",2500 + System.currentTimeMillis());
    		onStageReset();
    		return;
		}
    	else if (e instanceof Zombie)
    		gameMgr.addScore(100);
    	else if (e instanceof Swordsman)
    		gameMgr.addScore(250);
    	else if (e instanceof Soldier)
    		gameMgr.addScore(500);
		entityMgr.destroyEntity(e.getHandle());		
	}
	
	public void processAI(){
		for (int i = 0; i < entityMgr.getEnemyList().size(); i++) {
			EnemyEntity ent = entityMgr.getEnemyList().get(i);
			if (ent instanceof Zombie)
				((Zombie)ent).think(entityMgr, System.currentTimeMillis());
			else if (ent instanceof Swordsman)
				((Swordsman)ent).think(entityMgr, System.currentTimeMillis());
			else if (ent instanceof Soldier)
				((Soldier)ent).think(entityMgr, System.currentTimeMillis(), platformMgr);
			else if (ent instanceof Boss)
				((Boss)ent).think(entityMgr, System.currentTimeMillis(), platformMgr);
		}
	}
	
	public void onStageReset(){
		entityMgr.destroyAll();
		platformMgr.resetPlatformList();
	}
	
	public void processCollisions(){
		PlayerEntity pc = entityMgr.getPlayerEntity();
		
		for (int i = 0; i < entityMgr.getBulletList().size(); i++){
			Bullet b = entityMgr.getBulletList().get(i); 
			
			for (int k = 0; k < entityMgr.getCrateList().size(); k++)
			{
				Crate c = entityMgr.getCrateList().get(k);
				if(b.doesCollide(c) && !b.isAlreadyHit(c)){
					b.hit(c);
					if (entityMgr.getPlayerEntity().getWeaponName() == "handgun" || c.createRandomWeapon() == 1){
						int type = c.createRandomWeapon();
						if (type == Weapon.TYPE_PISTOL)
						{
							entityMgr.createPistol(c.getX(),c.getY());
						}
						else if (type == Weapon.TYPE_SHOTGUN)
						{
							entityMgr.createShotgun(c.getX(),c.getY());
						}
						else if (type == Weapon.TYPE_LMG)
						{
							entityMgr.createLMG(c.getX(),c.getY());
						}
						else if (type == Weapon.TYPE_ROCKET)
						{
							entityMgr.createRocketLauncher(c.getX(),c.getY());
						}
						else if (type == Weapon.TYPE_CHASER)
						{
							entityMgr.createChaser(c.getX(),c.getY());
						}
					}
					else {
						entityMgr.createAmmo(c.getX(),c.getY());
					}
					entityMgr.destroyEntity(c.getHandle());
					int pen = b.getPenetrate();
					if (pen != -1){
						pen--;
						if (pen == -1)
							entityMgr.destroyEntity(b.getHandle());
						else
							b.setPenetrate(pen);
					}
				}
			}
			
			for (int k = 0; k < entityMgr.getEnemyList().size(); k++)
			{
				EnemyEntity e = entityMgr.getEnemyList().get(k);
				if(b.doesCollide(e) && b.getTeam() == 0 && !b.isAlreadyHit(e)){
					b.hit(e);
					int pen = b.getPenetrate();
					int dam = b.getDamage();
					if (pen != -1){
						pen--;
						if (pen == -1)
							entityMgr.destroyEntity(b.getHandle());
						else
							b.setPenetrate(pen);
					}
					if (e.reduceHealth(dam))
						onEntityDeath(e);
				}
			}
			
			if(b.doesCollide(pc) && b.getTeam() == 1 && !b.isAlreadyHit(pc)){
				b.hit(pc);
				int pen = b.getPenetrate();
				if (pen != -1){
					pen--;
					if (pen == -1)
						entityMgr.destroyEntity(b.getHandle());
					else
						b.setPenetrate(pen);
				}
				if (pc.reduceHealth(1))
					onEntityDeath(pc);
			}
		}
		
		for (int k = 0; k < entityMgr.getEnemyList().size(); k++)
		{
			EnemyEntity e = entityMgr.getEnemyList().get(k);
			
			if(e.doesCollide(pc)){
				if (pc.reduceHealth(1))
					onEntityDeath(pc);
				return;
			}
		}
		
		for (int k = 0; k < entityMgr.getWeaponList().size(); k++)
		{
			Weapon w = entityMgr.getWeaponList().get(k);
			
			if(w.doesCollide(pc)){
				if (w instanceof Ammo){
					int bCount = pc.getBulletCount();
					if (bCount != -1){
						int maxB = pc.getMaxBullet();
						int newCount = bCount + maxB/2;
						if (newCount > maxB)
							pc.setBulletCount(maxB);
						else
							pc.setBulletCount(newCount);
					}
				}
				else{
					pc.setBullet(w.getBulletData());
					pc.setMaxBullet(w.getMagSize());
					pc.setBulletCount(w.getMagSize());
					pc.setCooldown(w.getCooldown());
					pc.setWeaponName(w.getName());
				}
				entityMgr.destroyEntity(w.getHandle());
			}
		}
	}
	
	public static void main(String[] args) {
		GameEngine engine = new GameEngine();
		engine.gameLoop();
	}

	
}
